
#include <vfs.h>

#include <gfx/idevice.h>
#include <gfx/image.h>
#include <gfx/ipatchmesh.h>
#include <gfx/ivertexdeclaration.h>
#include <gfx/vertex.h>
#include <gfx/generator/grassinstancegenerator.h>
#include <gfx/mesh.h>

#include <ldr/loadingsystem.h>

#include <math/boundingbox.h>

#include <utils/meshcreator.h>
#include <utils/lodgenerator.h>

iPatchMesh* create_terrain (iDevice* device, const char* filename, bool invertP, bool invertQ, float dp, float dq)
{
	ceMeshCreator mc;
	if (!mc.Initialize(device))
		{
			return 0;
		}

	ceImage* image = ceLoadingSystem::Get ()->LoadImage ("", ceVFS::Get()->Open (filename));
	if (!image)
		{
			printf ("Unable to open image File: %s\n", filename); fflush (stdout);
			return 0;
		}

	float* heightData = new float [image->GetWidth() * image->GetHeight()];
	float* hptr = heightData;
	float maxH = 0.0f;
//  maxH = 0.0f;
	for (unsigned q=0; q<image->GetHeight(); q++)
		{
			for (unsigned p=0; p<image->GetWidth(); p++)
				{
					unsigned rgb = image->GetRGB(p, q);

					*hptr++ = ((float)(rgb & 0xff) / 255.0f) * maxH;
				}
		}

	iPatchMesh* mesh = mc.CreateTerrain (
		ceVector3f (-dp/2.0f, -dq/2.0f, 0.0f),
		ceVector3f ( dp/2.0f,  dq/2.0f, 1.0f),
		image->GetWidth (),
		image->GetHeight (),
		heightData,
		4,
		4,
		100,
		invertP,
		invertQ,
		false);
	delete [] heightData;
	if (mesh)
		{
			mesh->CalcSmoothNormals ();
		}
	return mesh;
}


iPatchMesh* create_sphere (iDevice* device, float radius, unsigned numP)
{
	ceVertexElement vertexElements []  =
		{ ceVertexElement(VSD_Position0, DT_Float, 3, 0,  32, 0),
			ceVertexElement(VSD_Normal0, DT_Float, 3, 12, 32, 0),
			ceVertexElement(VSD_TexCoord0, DT_Float, 2, 24, 32, 0),
			ceVertexElement()
		};
	iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);

	unsigned numQ = ((numP - 1) >> 1) + 1;

	float* vertices = new float[numP*numQ*8];
	float* vptr=vertices;
	for (unsigned q=0; q<numQ; q++)
		{
			float angQ = (float)q * M_PI / (float)(numQ-1);
			float tv = (float)q / (float)(numQ-1);
			for (unsigned p=0; p<numP; p++)
				{
					float angP = (float)p * (float)M_PI * 2.0f / (float)(numP - 1);
					float tu = (float)p / (float)(numP-1);
					ceVector3f po;
					po.x = sin(angP) * radius * sin(angQ);
					po.y = cos(angQ) * radius * 1.0f;
					po.z = cos(angP) * radius * sin(angQ);
					ceVector3f n = po.Normalized();

					*vptr++ = po.x;
					*vptr++ = po.y;
					*vptr++ = po.z;

					*vptr++ = n.x;
					*vptr++ = n.y;
					*vptr++ = n.z;

					*vptr++ = tu;
					*vptr++ = tv;
				}
		}


	unsigned splits = 8;

	iVertexBuffer* vb = device->CreateVertexBuffer(numP*numQ*8*sizeof(float), vertices, BDM_Static);
	delete [] vertices;

	iPatchMesh* patch = device->CreatePatchMesh();
	if (!patch->Initialize (device, vb, numP, numQ, vertDecl, splits, splits>>1, radius * 10.0, true, true))
		{
			delete patch;
			patch = 0;
		}
	return patch;
}

iMesh* create_grass_mesh (iDevice* device,
													float radius,
													float height,
													unsigned num,
													const char* filename,
													const char* densityFilename,
													float p0,
													float p1,
													float q0,
													float q1,
													float dp,
													float dq,
													bool invertP,
													bool invertQ)
{
	static unsigned grass_counter = 0;

	ceImage* image = ceLoadingSystem::Get()->LoadImage("", ceVFS::Get()->Open (filename));
	if (!image)
		{
			printf ("Unable to open image File\n"); fflush (stdout);
			return 0;
		}
	ceImage* densImage = ceLoadingSystem::Get()->LoadImage("", ceVFS::Get()->Open(densityFilename));
	if (!densImage)
		{
			printf ("Unable to open density image File\n"); fflush (stdout);
			return 0;
		}
	ceVertexElement vertexElements []  =
		{ ceVertexElement(VSD_Position0, DT_Float, 3, 0,  32, 0),
			ceVertexElement(VSD_Normal0,	 DT_Float, 3, 12, 32, 0),
			ceVertexElement(VSD_TexCoord0, DT_Float, 2, 24, 32, 0),
			ceVertexElement()
		};
	iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);

	unsigned vertexSize = 3 + 3 + 2;
	unsigned numVerts = 4;

	unsigned faceSize = 3;
	unsigned numFaces = 2;

	float* vertices = new float[vertexSize * numVerts * 3];
	unsigned short* indices = new unsigned short[numFaces * faceSize * 3];

#define GETV(j, r)   vertices[(i*numVerts + j)*vertexSize + r]
#define SETV(j, r, v) GETV(j, r) = v
#define VERT(j,x,y,z,u,v) SETV(j, 0, c*x); SETV(j, 1, y), SETV(j, 2, s*z); SETV(j, 3, 0); SETV(j, 4, 0); SETV(j, 5, 1); SETV(j, 6, u); SETV(j, 7, v)


#define SETI(r,v) indices[i*(numFaces*faceSize) + r] = i * numVerts + v
#define IND(i0, i1, i2, i3) SETI(0, i0); SETI(1, i1); SETI(2, i2); SETI(3, i0); SETI(4, i2); SETI(5, i3)
//                            SETI(6, i0); SETI(7, i2); SETI(8, i1); SETI(9, i0); SETI(10, i3); SETI(11, i2);
	for (unsigned i=0; i<3; i++)
		{
			float a = (float)i * (float)M_PI / 3.0f;
			float c = cos(a);
			float s = sin(a);

			IND(0, 1, 2, 3);
			VERT(0, -radius,  radius, 0,      0, 1);
			VERT(1, -radius,  radius, height, 0, 0);
			VERT(2,  radius, -radius, height, 1, 0);
			VERT(3,  radius, -radius, 0,      1, 1);

		}
#undef GETV
#undef SETV
#undef VERT
#undef SETI
#undef IND

	ceBoundingBox bbox;
	bbox.Add(ceVector3f (-radius, -radius, -radius));
	bbox.Add(ceVector3f (radius, radius, radius));
	bbox.Update();


	iVertexBuffer* vb = device->CreateVertexBuffer(vertexSize*numVerts*3*sizeof(float), vertices, BDM_Static);
	delete [] vertices;

	iIndexBuffer* ib = device->CreateIndexBuffer(numFaces*faceSize*3*sizeof(unsigned short), indices, BDM_Static);
	delete [] indices;

	ceVector3f* instancePositions = new ceVector3f [num];
	ceVector3f* vptr = instancePositions;
	float maxH = 20.0f;
	maxH = 0.0f;
	srand(0);
	ceBoundingBox ibbox;
	bbox.Clear();
#define RANDOM(min,max) ((float)(min) + ((float)rand () / (float)RAND_MAX) * (float)((max)-(min)))
	unsigned realNum = 0;
	for (unsigned i=0; i<num; i++)
		{
			float fip = RANDOM(p0, p1);
			float fiq = RANDOM(q0, q1);

			unsigned pos_p = fip * image->GetWidth();
			unsigned pos_q = fiq * image->GetHeight();
			if (invertP)
				{
					pos_p = image->GetWidth () - pos_p;
				}
			if (invertQ)
				{
					pos_q = image->GetHeight () - pos_q;
				}
			unsigned rgb = image->GetRGB(pos_p, pos_q);
			unsigned dens = densImage->GetRGB(fip * image->GetWidth(), fiq * image->GetHeight()) & 0xff;
			unsigned dr = RANDOM(0x00, 0xff);
			if (dr >= dens)
				{
					continue;
				}

			vptr->x = (dp/-2.0) + fip * dp;
			vptr->y = -((dq/-2.0) + fiq * dq);
			vptr->z = ((float)(rgb & 0xff) / 255.0f) * maxH;
			ibbox.Add (*vptr);
			vptr++;
			realNum++;
		}
#undef RANDOM
	ibbox.AddMargin (ceVector3f (radius, radius, radius));
	ibbox.Update ();

	ceMesh* mesh = new ceMesh();
	mesh->SetVertexDeclaration(vertDecl);
	mesh->SetVertices(vb);
	mesh->SetIndices(ib);
	mesh->Set32BitIndices(false);
	mesh->SetBoundingBox(bbox);
	mesh->SetNumberOfTrigons(3 * numFaces);


	iInstancedMesh* instancedMesh = device->CreateInstancedMesh();
	instancedMesh->SetMesh(mesh);
	instancedMesh->SetBoundingBox (ibbox);

	ceInstancedData id;
	id.Binding			=	ceProgramParameterName ("INSTANCEPOSITION");
	id.Type					= IDT_Vector4;
	id.Data         = 0;
	instancedMesh->AddInstanceData (id);

	ceGrassInstanceGenerator* gig = new ceGrassInstanceGenerator (grass_counter++);
	gig->SetVertices (instancePositions, realNum);
	instancedMesh->SetInstanceGenerator(gig);

	ceLODGenerator lodG;
	instancedMesh->SetLevelOfDetailSolver(lodG.CreateLinearSolver(instancedMesh->GetBoundingBox(), dp / 4.0f, false));


	return instancedMesh;
}




iMesh* create_plane_mesh (iDevice* device, float x, float y)
{
	ceVertexElement vertexElements []  =
		{ ceVertexElement(VSD_Position0,	DT_Float, 3, 0,  56, 0),
			ceVertexElement(VSD_Normal0,		DT_Float, 3, 12, 56, 0),
			ceVertexElement(VSD_TexCoord0,	DT_Float, 2, 24, 56, 0),
			ceVertexElement(VSD_Tangent0,		DT_Float, 3, 32, 56, 0),
			ceVertexElement(VSD_Binormal0,	DT_Float, 3, 44, 56, 0),

			ceVertexElement()
		};

	float vertices[] =
	{
		-x, -y, 0,    0, 0, 1,      0,  0,    2*x, 0, 0,   0, 2*y, 0,
		-x,  y, 0,    0, 0, 1,      0,  10,   2*x, 0, 0,   0, 2*y, 0,
		 x, -y, 0,    0, 0, 1,      10, 0,    2*x, 0, 0,   0, 2*y, 0,
		 x,  y, 0,    0, 0, 1,      10, 10,   2*x, 0, 0,   0, 2*y, 0,
	};

	unsigned short indices[] =
	{
		0, 1, 3,
		0, 3, 2
	};


	iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);
	iVertexBuffer* vertexBuffer = device->CreateVertexBuffer(sizeof(vertices), vertices, BDM_Static);
	iIndexBuffer* indexBuffer = device->CreateIndexBuffer(sizeof(indices), indices, BDM_Static);

	ceBoundingBox bbox;
	bbox.Clear();
	bbox.Add(ceVector3f (-x, -y, -1));
	bbox.Add(ceVector3f ( x,  y,  1));
	bbox.Update();

	ceMesh* mesh = new ceMesh();
	mesh->SetVertexDeclaration(vertDecl);
	mesh->SetVertices(vertexBuffer);
	mesh->SetIndices(indexBuffer);
	mesh->Set32BitIndices(false);
	mesh->SetBoundingBox(bbox);
	mesh->SetNumberOfTrigons(2);

	iStaticMesh* staticMesh = device->CreateStaticMesh();
	staticMesh->SetMesh(mesh);
	return staticMesh;
}

/*
iMesh* create_particle_mesh (iDevice* device, float size)
{
	ceVertexElement vertexElements []  =
		{ ceVertexElement(VB_Position, 0, 3, 0,  20, 0),
			ceVertexElement(VB_TexCoord, 0, 2, 12, 20, 0),
			ceVertexElement(VB_Undefined)
		};
	iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);

	float vertices[] = {
		-size, -size, 0,    0, 0,
		-size,  size, 0,    0, 1,
		 size,  size, 0,    1, 1,
		 size, -size, 0,    1, 0
	};

	unsigned short indices[] = {
		0, 1, 2,
		0, 2, 3
	};

	iVertexBuffer* vb = device->CreateVertexBuffer(sizeof(vertices), vertices, BDM_Static);
	iIndexBuffer* ib = device->CreateIndexBuffer(sizeof(indices), indices, BDM_Static);

	ceBoundingBox bbox;
	bbox.Clear();
	bbox.Add (ceVector3 (-size, -size, 0));
	bbox.Add (ceVector3 ( size,  size, 0));
	bbox.Update();

	ceMesh mesh;
	mesh.SetVertexDeclaration(vertDecl);
	mesh.SetVertices(vb);
	mesh.SetIndices(ib);
	mesh.Set32BitIndices(false);
	mesh.SetBoundingBox(bbox);
	mesh.SetNumberOfTrigons(2);

	ceBoundingBox ibbox;
	ibbox.Clear ();
	ibbox.Add (ceVector3 (-1000, -1000, -1000));
	ibbox.Add (ceVector3 ( 1000,  1000,  1000));
	ibbox.Update();

	iInstancedMesh* instancedMesh = engine.CreateInstancedMesh();
	instancedMesh->SetMesh(mesh);
	instancedMesh->SetBoundingBox (ibbox);

	ceParticleGenerator* gig = new ceParticleGenerator (100);
	instancedMesh->SetInstanceGenerator(gig);

	return instancedMesh;
}
*/


iMesh* create_static_plane (iDevice* device, float u, float v)
{
	ceVertexXYZNT2 vertices [] =
		{
			{ ceVector3f (-u, 0,  v), ceVector3f (0, 1, 0), ceVector2f (0, 0) },
			{ ceVector3f (-u, 0, -v), ceVector3f (0, 1, 0), ceVector2f (0, 1) },
			{ ceVector3f ( u, 0, -v), ceVector3f (0, 1, 0), ceVector2f (1, 1) },
			{ ceVector3f ( u, 0,  v), ceVector3f (0, 1, 0), ceVector2f (1, 0) },
		};
	unsigned short indices [] =
		{
			0, 1, 2,
			0, 2, 3,
		};

	ceVertexElement vertexElements []  =
		{
			ceVertexElement(VSD_Position0, DT_Float, 3, 0,  32, 0),
			ceVertexElement(VSD_Normal0, DT_Float, 3, 12, 32, 0),
			ceVertexElement(VSD_TexCoord0, DT_Float, 2, 24, 32, 0),
			ceVertexElement()
		};
	iVertexDeclaration* vertDecl = device->CreateVertexDeclaration(vertexElements);
	iVertexBuffer* vb = device->CreateVertexBuffer(sizeof (vertices), vertices, BDM_Static);
	iIndexBuffer* ib = device->CreateIndexBuffer(sizeof(indices), indices, BDM_Static);
	ceMesh* mesh = new ceMesh();
	mesh->Set32BitIndices(false);
	mesh->SetVertexDeclaration(vertDecl);
	mesh->SetVertices (vb);
	mesh->SetIndices(ib);
	mesh->SetNumberOfTrigons(2);

	ceBoundingBox bbox;
	bbox.Clear();
	bbox.Add (-u, 0, -v);
	bbox.Add ( u, 0,  v);
	bbox.Update();
	mesh->SetBoundingBox (bbox);


	iStaticMesh* sm = device->CreateStaticMesh();
	sm->SetMesh (mesh);

	return sm;
}



ceMesh* create_cylinder_mesh (iDevice* device, float radius, float height, float numSegs, float numRads, float tuSplit, float tvSplit)
{
	ceVertexElement vertexElements []  =
		{ ceVertexElement(VSD_Position0,	DT_Float, 3, 0,  32, 0),
			ceVertexElement(VSD_Normal0,		DT_Float, 3, 12, 32, 0),
			ceVertexElement(VSD_TexCoord0,	DT_Float, 2, 24, 32, 0),
			ceVertexElement()
		};

	float minHeight = -height / 2.0f;
	unsigned numVerts = (numRads+1) * (numSegs + 1) + (numRads + numRads + 2) + (numRads + numRads + 2);
	ceVertexXYZNT2 *vertices = new ceVertexXYZNT2 [numVerts];
	ceVertexXYZNT2 *vptr = vertices;
	for (unsigned s=0; s<=numSegs; ++s)
		{
			float h = (float)s / (float)numSegs;
			for (unsigned r=0; r<=numRads; ++r, ++vptr)
				{
					float alpha = (float)r / (float)numRads;
					float angle = alpha * 2.0f * M_PI;

					float ca = (float)cos (angle);
					float sa = (float)sin (angle);

					vptr->v  = ceVector3f (radius * ca, radius * sa, minHeight + height * h);
					vptr->n  = ceVector3f (ca, sa, 0.0f);
					vptr->t0 = ceVector2f (alpha * tuSplit, h * tvSplit);
				}
		}

	for (unsigned r=0; r<=numRads; ++r, vptr++)
		{
			vptr->v = ceVector3f (0.0f, 0.0f, minHeight + height);
			vptr->n = ceVector3f (0, 0, 1);
			vptr->t0 = ceVector2f ((float)r / (float)(numRads), 1.0f);
		}
	for (unsigned r=0; r<=numRads; ++r, vptr++)
		{
			float angle = (float)r * 2.0f * M_PI / (float)numRads;
			vptr->v = ceVector3f (radius * (float)cos (angle), radius * (float)sin (angle), minHeight + height);
			vptr->n = ceVector3f (0, 0, 1);
			vptr->t0 = ceVector2f ((float)r / (float)(numRads), 0.0f);
		}

	for (unsigned r=0; r<=numRads; ++r, vptr++)
		{
			vptr->v = ceVector3f (0.0f, 0.0f, minHeight);
			vptr->n = ceVector3f (0, 0, -1);
			vptr->t0 = ceVector2f ((float)r / (float)(numRads), 0.0f);
		}
	for (unsigned r=0; r<=numRads; ++r, vptr++)
		{
			float angle = (float)r * 2.0f * M_PI / (float)numRads;
			vptr->v = ceVector3f (radius * (float)cos (angle), radius * (float)sin (angle), minHeight);
			vptr->n = ceVector3f (0, 0, -1);
			vptr->t0 = ceVector2f ((float)r / (float)(numRads), 1.0f);
		}

	unsigned numIndices = numRads * numSegs * 6 + numRads * 3 + numRads * 3;
	unsigned short *indices = new unsigned short [numIndices];
	unsigned short *iptr = indices;
	for (unsigned s=0; s<numSegs; ++s)
		{
			unsigned short i0 = s * (numRads+1);
			unsigned short i1 = i0 + (numRads+1);
			for (unsigned r=0; r<numRads; ++r)
				{
					unsigned r1 = r+1;

					unsigned short i00 = i0 + r;
					unsigned short i01 = i0 + r1;
					unsigned short i10 = i1 + r;
					unsigned short i11 = i1 + r1;

					*iptr++ = i00;
					*iptr++ = i10;
					*iptr++ = i11;

					*iptr++ = i00;
					*iptr++ = i11;
					*iptr++ = i01;
				}
		}

	unsigned r0 = (numRads+1) * (numSegs + 1);
	unsigned r1 = (numRads+1) * (numSegs + 1) + (numRads+1);
	for (unsigned r=0; r<numRads; ++r)
		{
			unsigned ra = r+1;

			*iptr++ = r0 + r;
			*iptr++ = r1 + ra;
			*iptr++ = r1 + r;
		}

	r0 = (numRads+1) * (numSegs + 1) + (numRads+1) + (numRads+1);
	r1 = (numRads+1) * (numSegs + 1) + (numRads+1) + (numRads+1) + (numRads+1);
	for (unsigned r=0; r<numRads; ++r)
		{
			unsigned ra = r+1;

			*iptr++ = r0 + r;
			*iptr++ = r1 + r;
			*iptr++ = r1 + ra;
		}


	iVertexBuffer				*vb = device->CreateVertexBuffer(sizeof(ceVertexXYZNT2) * numVerts, vertices, BDM_Static);
	iIndexBuffer				*ib = device->CreateIndexBuffer(sizeof (unsigned short) * numIndices, indices, BDM_Static);
	iVertexDeclaration  *vd = device->CreateVertexDeclaration(vertexElements);

	ceBoundingBox bbox;
	bbox.Clear();
	bbox.Add(ceVector3f (-radius, -radius, minHeight));
	bbox.Add(ceVector3f (radius, radius, minHeight + height));
	bbox.Update();

	ceMesh* mesh = new ceMesh();
	mesh->SetVertexDeclaration(vd);
	mesh->SetVertices(vb);
	mesh->SetIndices(ib);
	mesh->Set32BitIndices(false);
	mesh->SetBoundingBox(bbox);
	mesh->SetNumberOfTrigons(numIndices / 3);
	return mesh;
}


iStaticMesh* create_cylinder(iDevice* device, float radius, float height, float numSegs, float numRads, float tuSplit, float tvSplit)
{
	ceMesh *mesh = create_cylinder_mesh(device, radius, height, numSegs, numRads, tuSplit, tvSplit);
	iStaticMesh* staticMesh = device->CreateStaticMesh();
	staticMesh->SetMesh(mesh);
	return staticMesh;
}
